
#include <stdio.h>
#include <string.h>

#include <sstream>
#include <iostream>
#include <iomanip>

#include <boost/bind.hpp>
#include <boost/thread/thread.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <boost/filesystem.hpp>

#include <boost/program_options.hpp>
namespace po = boost::program_options;

using namespace boost::posix_time;

#include "fekcab/client.hpp"

#define	N_ROUND	10000

float
getTimeDuration( ptime time_begin, ptime time_end )
{
    time_period tp( time_begin, time_end );
    time_duration   td = tp.length();
    long    time_last_ms = td.total_milliseconds();
    float   time_last_s = time_last_ms / 1000.0f;

    return time_last_s;
}


void
testfunc( const std::string& appconf, int times, bool verbose, int table_id )
{
	char	table_name[ 32 ];
	snprintf(table_name, sizeof(table_name) - 1, "cltTest%09u", table_id );

	fekcab::client::Client	clt( appconf.c_str() );

	std::string	TABLE_COUNT( "TCount" );
	std::string	TABLE_FISH( "TFish" );
	std::string	TABLE_BIRD( "TBird" );

	{
		ptime   time_start = microsec_clock::local_time();
		ptime	time_start_R = time_start;
		u_int32_t	count_down_R = N_ROUND;

		std::string	key_count( "count" );


		{
			std::cerr << "BEFORE INSERT ...\n";
			try {
				int64_t	result = clt.inc( TABLE_COUNT, key_count, 0 );
				if ( result != times ) {
					std::cerr << " - " << TABLE_COUNT << "." << key_count << " = " << result << ", expect " << times << std::endl;
				}
			}
			catch( std::exception& e ) {
				std::cerr << "fail to INC. Exception : " << e.what() << std::endl;
				return;
			}

			std::ostringstream	oss_key_fish;
			oss_key_fish << "KEY-FISH-" << std::setw(8) << std::setfill('0') << times;
			std::ostringstream	oss_data_fish;
			oss_data_fish << "DATA-FISH-" << std::setw(8) << std::setfill('0') << times;

			std::ostringstream	oss_key_bird;
			oss_key_bird << "KEY-BIRD-" << std::setw(8) << std::setfill('0') << times;
			std::ostringstream	oss_data_bird;
			oss_data_bird << "DATA-BIRD-" << std::setw(8) << std::setfill('0') << times;

			std::vector< fekcab::client::kv_type >	kvs;
			fekcab::client::kv_type	kv_fish( TABLE_FISH, oss_key_fish.str(), "" );
			kvs.push_back( kv_fish );
			fekcab::client::kv_type	kv_bird( TABLE_FISH, oss_key_bird.str(), "" );
			kvs.push_back( kv_bird );
			
			int	rc;
			try {
				rc= clt.find( kvs );
			}
			catch( std::exception& e ) {
				std::cerr << "fail to FIND. Exception : " << e.what() << std::endl;
				return;
			}
			if ( rc < 0 ) {
				std::cerr << "fail to FIND key-values" << std::endl;
				return;
			}

			const fekcab::client::kv_type&	kv_fish_ret = kvs[0];
			if ( oss_data_fish.str() != kv_fish_ret.value ) {
				std::cerr << "BAD result for KEY " << oss_key_fish.str() << " -- GOT '" << kv_fish_ret.value << "', WANT " << oss_data_fish.str() << std::endl;
			}
			const fekcab::client::kv_type&	kv_bird_ret = kvs[1];
			if ( oss_data_bird.str() != kv_bird_ret.value ) {
				std::cerr << "BAD result for KEY " << oss_key_bird.str() << " -- GOT '" << kv_bird_ret.value << "', WANT " << oss_data_bird.str() << std::endl;
			}
		}


		std::cerr << "INSERTing ...\n";
		// clear
		int64_t	current = clt.inc( TABLE_COUNT, key_count, 0 );
		current = clt.dec( TABLE_COUNT, key_count, current );
		if ( current != 0 ) {
			std::cerr << "fail to clear counter " << TABLE_COUNT << "." << key_count << std::endl;
		}

		for (int i=0; i<times; i++) {
			int64_t	delta = 1;

			try {
				int64_t	result = clt.inc( TABLE_COUNT, key_count, delta );
				//std::cerr << " - after INC " << delta << " return " << result << std::endl;
				result = 0;
			}
			catch( std::exception& e ) {
				std::cerr << "fail to INC. Exception : " << e.what() << std::endl;
				return;
			}

			std::ostringstream	oss_key_fish;
			oss_key_fish << "KEY-FISH-" << std::setw(8) << std::setfill('0') << i + 1;
			std::ostringstream	oss_data_fish;
			oss_data_fish << "DATA-FISH-" << std::setw(8) << std::setfill('0') << i + 1;

			std::ostringstream	oss_key_bird;
			oss_key_bird << "KEY-BIRD-" << std::setw(8) << std::setfill('0') << i + 1;
			std::ostringstream	oss_data_bird;
			oss_data_bird << "DATA-BIRD-" << std::setw(8) << std::setfill('0') << i + 1;

			std::vector< fekcab::client::kv_type >	kvs;
			fekcab::client::kv_type	kv_fish( TABLE_FISH, oss_key_fish.str(), oss_data_fish.str() );
			kvs.push_back( kv_fish );
			fekcab::client::kv_type	kv_bird( TABLE_FISH, oss_key_bird.str(), oss_data_bird.str() );
			kvs.push_back( kv_bird );
			
			int	rc;
			try {
				rc= clt.insert( kvs );
			}
			catch( std::exception& e ) {
				std::cerr << "fail to insert. Exception : " << e.what() << std::endl;
				return;
			}
			if ( rc < 0 ) {
				std::cerr << "fail to insert key-values" << std::endl;
				return;
			}

			--count_down_R;
			if (0 == count_down_R) {
				ptime   time_stop_R = microsec_clock::local_time();
				float   time_last_s = getTimeDuration( time_start_R, time_stop_R );
				float   tps = N_ROUND / time_last_s;
				std::cerr << "  R-PUT " << N_ROUND << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

				time_start_R = time_stop_R;
				count_down_R = N_ROUND;
			}
		}


		{
			std::cerr << "AFTER INSERT ...\n";
			try {
				int64_t	result = clt.inc( TABLE_COUNT, key_count, 0 );
				if ( result != times ) {
					std::cerr << " - " << TABLE_COUNT << "." << key_count << " = " << result << ", expect " << times << std::endl;
				}
			}
			catch( std::exception& e ) {
				std::cerr << "fail to INC. Exception : " << e.what() << std::endl;
				return;
			}

			std::ostringstream	oss_key_fish;
			oss_key_fish << "KEY-FISH-" << std::setw(8) << std::setfill('0') << times;
			std::ostringstream	oss_data_fish;
			oss_data_fish << "DATA-FISH-" << std::setw(8) << std::setfill('0') << times;

			std::ostringstream	oss_key_bird;
			oss_key_bird << "KEY-BIRD-" << std::setw(8) << std::setfill('0') << times;
			std::ostringstream	oss_data_bird;
			oss_data_bird << "DATA-BIRD-" << std::setw(8) << std::setfill('0') << times;

			std::vector< fekcab::client::kv_type >	kvs;
			fekcab::client::kv_type	kv_fish( TABLE_FISH, oss_key_fish.str(), "" );
			kvs.push_back( kv_fish );
			fekcab::client::kv_type	kv_bird( TABLE_FISH, oss_key_bird.str(), "" );
			kvs.push_back( kv_bird );
			
			int	rc;
			try {
				rc= clt.find( kvs );
			}
			catch( std::exception& e ) {
				std::cerr << "fail to FIND. Exception : " << e.what() << std::endl;
				return;
			}
			if ( rc < 0 ) {
				std::cerr << "fail to FIND key-values" << std::endl;
				return;
			}

			const fekcab::client::kv_type&	kv_fish_ret = kvs[0];
			if ( oss_data_fish.str() != kv_fish_ret.value ) {
				std::cerr << "BAD result for KEY " << oss_key_fish.str() << " -- GOT '" << kv_fish_ret.value << "', WANT " << oss_data_fish.str() << std::endl;
			}
			const fekcab::client::kv_type&	kv_bird_ret = kvs[1];
			if ( oss_data_bird.str() != kv_bird_ret.value ) {
				std::cerr << "BAD result for KEY " << oss_key_bird.str() << " -- GOT '" << kv_bird_ret.value << "', WANT " << oss_data_bird.str() << std::endl;
			}
		}

		//ptime   time_stop = microsec_clock::local_time();
		//float   time_last_s = getTimeDuration( time_start, time_stop );
		//float   tps = times / time_last_s;
		//std::cerr << "INC/DEC " << times << " records last " << std::setprecision(3) << std::fixed << time_last_s << " seconds, tps=" << tps << std::resetiosflags(std::ios::floatfield) << std::setprecision(6) << std::endl;

		std::cerr << "done.\n";
	}

}


int		main(int argc, char* argv[])
{

    std::string appconf;
    //std::string logconf;
    int times = 0;
    int thread_count = 0;
    bool verbose = false;

    try {

        po::options_description desc("Test Insert/Find/Erase   Allowed options");
        desc.add_options()
            ("help,h", "produce help message")
            ("verbose,v", "output verbose message")
            ("app-conf", po::value<std::string>(&appconf)->default_value("app.conf"), "application config filename")
            //("log-conf", po::value<std::string>(&logconf)->default_value("log.conf"), "log config filename")
            ("times", po::value<int>(&times)->default_value(10), "number of request to send")
            ("threads", po::value<int>(&thread_count)->default_value(1), "number of threads to run")
        ;

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);
        po::notify(vm);

        if (vm.count("help")) {
            std::cout << desc << "\n";
            return 1;
        }

        if (vm.count("verbose")) {
            verbose = true;
        }

    }
    catch(std::exception& e) {
        std::cerr << "error: " << e.what() << "\n";
        return 1;
    }
    catch(...) {
        std::cerr << "Exception of unknown type!\n";
        return 2;
    }


    boost::filesystem::path appconf_path( appconf );
    if ( !boost::filesystem::exists( appconf_path ) ) { 
		std::cerr << "config file " << appconf << " does NOT exist.\n";
		return 3;
    }   
    if ( !boost::filesystem::is_regular_file( appconf_path ) ) { 
		std::cerr << "config file " << appconf << " is NOT a regular file.\n";
		return 4;
    }   

	
	/*
    boost::filesystem::path logconf_path( logconf );
    if ( !boost::filesystem::exists( logconf_path ) ) { 
		std::cerr << "config file " << logconf << " does NOT exist.\n";
		return 3;
    }   
    if ( !boost::filesystem::is_regular_file( logconf_path ) ) { 
		std::cerr << "config file " << logconf << " is NOT a regular file.\n";
		return 4;
    }   
	*/
	


    std::vector< boost::thread * >    threads;

    for (int i=0; i<thread_count; i++) {
        boost::thread*   thr = new boost::thread( boost::bind(testfunc, appconf, times, verbose, i) );

        threads.push_back( thr );
    }

    for (size_t i=0; i<threads.size(); i++) {
        boost::thread*  thr = threads[ i ];
        thr->join();
    }

    for (size_t i=0; i<threads.size(); i++) {
        boost::thread*  thr = threads[ i ];
        delete thr;
    }
    threads.clear();


	return	0;
}

